package com.gaj.util;

import com.gaj.entity.message.Message;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * FTP文件处理工具类
 * <p>
 * 职责：
 * 1. FTP连接管理
 * 2. 看守所单位文件处理
 * 3. XML文件解析
 * 4. ZIP文件解密处理
 * 5. 文件流管理
 * <p>
 * 使用场景：
 * 政法智能办案平台FTP服务器看守所单位文件处理，包括XML消息文件解析和ZIP数据文件解密
 *
 * @author 系统
 * @version 1.0
 */
@Component
public class FtpUtils {

    // ==================== FTP服务器配置常量 ====================

    /** FTP服务器地址 */
    private static final String FTP_SERVER = "192.168.100.102";

    /** FTP服务器端口 */
    private static final int FTP_PORT = 21;

    /** FTP用户名 */
    private static final String FTP_USER = "anonymous";

    /** FTP密码 */
    private static final String FTP_PASSWORD = "9789@qq.com";

    /** 远程根目录路径 */
    private static final String REMOTE_ROOT_PATH = "/bjcm";

    // ==================== 目录结构常量 ====================

    /** 消息接收目录路径 */
    private static final String RECEIVE_MSG_PATH = "/receive/msg";

    /** 数据接收目录路径 */
    private static final String RECEIVE_DATA_PATH = "/receive/data";

    // ==================== 主入口方法 ====================

    /**
     * 主入口方法 - 处理看守所单位的FTP文件
     *
     * 执行流程：
     * 1. 建立FTP连接
     * 2. 处理看守所单位的XML和ZIP文件
     * 3. 安全断开FTP连接
     *
     * @param args 命令行参数（暂未使用）
     */
    public static void main(String[] args) {
        FTPClient ftpClient = new FTPClient();
        try {
            // 建立FTP连接
            connectToFtpServer(ftpClient);

            // 处理看守所单位的文件
            processAllUnits(ftpClient);

        } catch (Exception e) {
            System.err.println("FTP文件处理执行失败: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // 确保FTP连接被正确关闭
            disconnectFromFtpServer(ftpClient);
        }
    }

    // ==================== FTP连接管理 ====================

    /**
     * 连接到FTP服务器
     *
     * 连接步骤：
     * 1. 建立TCP连接
     * 2. 用户身份验证
     * 3. 设置传输模式（被动模式）
     * 4. 设置文件类型（二进制）
     * 5. 切换到工作目录
     *
     * @param ftpClient FTP客户端实例
     * @throws IOException 连接失败时抛出异常
     */
    private static void connectToFtpServer(FTPClient ftpClient) throws IOException {
        System.out.println("正在连接FTP服务器: " + FTP_SERVER + ":" + FTP_PORT);

        // 1. 建立TCP连接
        ftpClient.connect(FTP_SERVER, FTP_PORT);

        // 检查连接状态
        if (!ftpClient.isConnected()) {
            throw new IOException("无法连接到FTP服务器: " + FTP_SERVER);
        }

        // 2. 用户身份验证
        boolean loginSuccess = ftpClient.login(FTP_USER, FTP_PASSWORD);
        if (!loginSuccess) {
            throw new IOException("FTP登录失败，请检查用户名和密码");
        }

        // 3. 设置传输模式为被动模式（避免防火墙问题）
        ftpClient.enterLocalPassiveMode();

        // 4. 设置文件传输类型为二进制（确保文件完整性）
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

        // 5. 切换到工作根目录
        if (!ftpClient.changeWorkingDirectory(REMOTE_ROOT_PATH)) {
            throw new IOException("无法切换到远程根目录: " + REMOTE_ROOT_PATH);
        }

        //System.out.println("FTP连接成功，当前工作目录: " + ftpClient.printWorkingDirectory());
    }

    /**
     * 安全断开FTP连接
     *
     * 断开步骤：
     * 1. 发送QUIT命令退出登录
     * 2. 关闭TCP连接
     * 3. 处理可能的异常
     *
     * @param ftpClient FTP客户端实例
     */
    private static void disconnectFromFtpServer(FTPClient ftpClient) {
        try {
            // 1. 发送QUIT命令，优雅退出登录
            if (ftpClient.isAvailable()) {
                ftpClient.logout();
            }

            // 2. 关闭TCP连接
            if (ftpClient.isConnected()) {
                ftpClient.disconnect();
            }

            System.out.println("FTP连接已安全断开");

        } catch (IOException e) {
            System.err.println("断开FTP连接时发生错误: " + e.getMessage());
        }
    }

    // ==================== 文件处理核心逻辑 ====================

    /**
     * 处理所有单位的FTP文件
     * <p>
     * 处理流程：
     * 1. 只处理看守所单位
     * 2. 处理看守所内的XML和ZIP文件
     *
     * @param ftpClient FTP客户端实例
     * @throws IOException 处理过程中发生IO异常
     */
    private static void processAllUnits(FTPClient ftpClient) throws IOException {
        // 只处理看守所单位
        String targetUnit = "kss"; // 看守所单位代码
        String unitPath = REMOTE_ROOT_PATH + "/" + targetUnit;

        // 检查看守所目录是否存在
        if (!ftpClient.changeWorkingDirectory(unitPath)) {
            System.out.println("看守所目录不存在或不可访问: " + unitPath);
            return;
        }

        // 切换回根目录
        ftpClient.changeWorkingDirectory(REMOTE_ROOT_PATH);

        // 处理看守所单位的文件
        processUnitFiles(ftpClient, targetUnit);
    }

    /**
     * 处理单个单位的文件
     * <p>
     * 处理流程：
     * 1. 构建单位目录路径
     * 2. 获取消息节点目录列表
     * 3. 遍历每个节点目录
     * 4. 处理节点内的XML和ZIP文件
     *
     * @param ftpClient FTP客户端实例
     * @param unitName 单位名称
     * @throws IOException IO异常
     */
    private static void processUnitFiles(FTPClient ftpClient, String unitName) throws IOException {
        // 构建单位目录路径
        String unitRoot = REMOTE_ROOT_PATH + "/" + unitName;
        String receiveMsgRoot = unitRoot + RECEIVE_MSG_PATH;  // 消息目录
        String receiveDataRoot = unitRoot + RECEIVE_DATA_PATH; // 数据目录

        System.out.println("  单位根目录: " + unitRoot);
        System.out.println("  消息目录: " + receiveMsgRoot);
        System.out.println("  数据目录: " + receiveDataRoot);

        // 获取消息节点目录列表
        FTPFile[] nodeDirs = ftpClient.listFiles(receiveMsgRoot);
        if (nodeDirs == null || nodeDirs.length == 0) {
            System.out.println("  单位 " + unitName + " 没有消息节点目录");
            return;
        }

        // 遍历每个节点目录
        for (FTPFile nodeDir : nodeDirs) {
            // 只处理目录，跳过文件
            if (!nodeDir.isDirectory()) {
                continue;
            }

            String nodeCode = nodeDir.getName();

            // 处理节点内的文件
            processNodeFiles(ftpClient, receiveMsgRoot, receiveDataRoot, nodeCode);
        }
    }

    /**
     * 处理单个节点的文件
     *
     * 处理流程：
     * 1. 构建消息和数据节点路径
     * 2. 获取消息节点下的XML文件列表
     * 3. 遍历每个XML文件
     * 4. 解析XML文件并匹配对应的ZIP文件
     *
     * @param ftpClient FTP客户端实例
     * @param receiveMsgRoot 消息根目录路径
     * @param receiveDataRoot 数据根目录路径
     * @param nodeCode 节点代码
     * @throws IOException IO异常
     */
    private static void processNodeFiles(FTPClient ftpClient, String receiveMsgRoot,
                                       String receiveDataRoot, String nodeCode) throws IOException {
        // 构建节点路径
        String msgNodePath = receiveMsgRoot + "/" + nodeCode;  // 消息节点路径
        String dataNodePath = receiveDataRoot + "/" + nodeCode; // 数据节点路径

        System.out.println("      消息节点路径: " + msgNodePath);
        System.out.println("      数据节点路径: " + dataNodePath);

        // 获取消息节点下的文件列表
        FTPFile[] msgFiles = ftpClient.listFiles(msgNodePath);
        if (msgFiles == null || msgFiles.length == 0) {
            System.out.println("      节点 " + nodeCode + " 没有消息文件");
            return;
        }

        // 遍历每个XML文件
        for (FTPFile xmlFile : msgFiles) {
            // 只处理文件，跳过目录
            if (!xmlFile.isFile()) {
                continue;
            }

            // 处理单个XML文件
            processXmlFile(ftpClient, msgNodePath, dataNodePath, xmlFile);
        }
    }

    // ==================== XML文件处理 ====================

    /**
     * 处理单个XML文件
     *
     * 处理流程：
     * 1. 读取XML文件内容，解析CFLJ字段（压缩包名）
     * 2. 验证ZIP文件名格式
     * 3. 查找对应的ZIP文件
     * 4. 如果找到ZIP文件，进行解密处理
     *
     * @param ftpClient FTP客户端实例
     * @param msgNodePath 消息节点路径
     * @param dataNodePath 数据节点路径
     * @param xmlFile XML文件对象
     */
    private static void processXmlFile(FTPClient ftpClient, String msgNodePath,
                                     String dataNodePath, FTPFile xmlFile) {
        // 1. 读取XML文件内容，获取CFLJ字段（压缩包名）
        String cflj = ReadXml.readXmlFile(ftpClient, msgNodePath, xmlFile);
        if (cflj == null) {
            System.out.println("          无法从XML文件中提取CFLJ字段");
            return;
        }

        // 2. 验证ZIP文件名格式
        if (!ZipVerification.verifyZipName(cflj)) {
            System.out.println("          ZIP文件名格式校验不通过: " + cflj);
            return;
        }

        // 3. 查找对应的ZIP文件
        try {
            if (fileExists(ftpClient, dataNodePath, cflj)) {
                // 4. 解密ZIP文件
                decryptZipFile(ftpClient, dataNodePath, cflj);

            } else {
                System.out.println("          未找到对应的ZIP文件: " + cflj);
            }
        } catch (IOException e) {
            System.err.println("          检查ZIP文件时发生错误: " + e.getMessage());
        }
    }

    // ==================== ZIP文件解密处理 ====================

    /**
     * 解密ZIP文件并进行完整性校验
     *
     * 解密流程：
     * 1. 获取ZIP文件的输入流
     * 2. 读取密文到字节数组
     * 3. 进行MD5完整性校验
     * 4. 使用SM4算法解密
     * 5. 校验解密后的ZIP结构
     *
     * @param ftpClient FTP客户端实例
     * @param dataNodePath 数据节点路径
     * @param cflj ZIP文件名
     */
    private static void decryptZipFile(FTPClient ftpClient, String dataNodePath, String cflj) {
        InputStream inputStream = null;
        try {
            // 1. 获取ZIP文件的输入流
            inputStream = getFileInputStream(ftpClient, dataNodePath, cflj);
            if (inputStream == null) {
                System.err.println("            无法获取ZIP文件流: " + cflj);
                return;
            }

            // 2. 读取密文为字节数组（确保完整读完）
            byte[] encryptedBytes = inputStreamToByteArray(inputStream);
            inputStream.close();
            inputStream = null;

            // 读取完并关闭流后，必须完成挂起命令
            completeFileTransfer(ftpClient);

            //System.out.println("            密文数据读取完成，大小: " + encryptedBytes.length + " 字节");

            // 3. MD5完整性校验（对密文做校验，与文件名中MD5一致）
            if (!ZipVerification.verifyZipCode(cflj, encryptedBytes)) {
                System.err.println("            MD5校验不通过: " + cflj);
                return;
            }

            // 4. 使用SM4算法解密到字节数组
            byte[] decryptedZipBytes = SM4ZipUtils.decryptZipToBytes(new ByteArrayInputStream(encryptedBytes));
            if (decryptedZipBytes == null || decryptedZipBytes.length == 0) {
                System.err.println("            解密ZIP文件失败: " + cflj);
                return;
            }
            //System.out.println("            SM4解密完成，解密后大小: " + decryptedZipBytes.length + " 字节");

            // 5. 校验解密后压缩包的结构
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedZipBytes)) {
                if (!ZipVerification.verifyZipStructure(byteArrayInputStream)) {
                    System.err.println("            ZIP结构校验不通过: " + cflj);
                }
            }

            //6.将校验结果自动反馈给发送单位

        } catch (Exception e) {
            System.err.println("            解密ZIP文件失败: " + cflj + ", 错误: " + e.getMessage());
        } finally {
            // 兜底处理：确保流被正确关闭，避免资源泄漏
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ignore) {
                    // 忽略关闭时的异常
                }
                completeFileTransfer(ftpClient);
            }
        }
    }

    // ==================== 工具方法 ====================

    /**
     * 将输入流转换为字节数组
     * <p>
     * 转换流程：
     * 1. 创建字节数组输出流
     * 2. 使用缓冲区循环读取输入流
     * 3. 将读取的数据写入输出流
     * 4. 返回完整的字节数组
     *
     * @param inputStream 输入流
     * @return 字节数组
     * @throws IOException IO异常
     */
    public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024]; // 1KB缓冲区
        int length;

        // 循环读取输入流数据
        while ((length = inputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, length);
        }

        return byteArrayOutputStream.toByteArray();
    }

    /**
     * 获取FTP服务器文件的输入流
     *
     * 获取流程：
     * 1. 切换到文件所在目录
     * 2. 获取文件的输入流
     * 3. 检查输入流是否有效
     *
     * @param ftpClient FTP客户端实例
     * @param filePath 文件所在目录路径
     * @param fileName 文件名
     * @return 文件输入流，失败返回null
     */
    public static InputStream getFileInputStream(FTPClient ftpClient, String filePath, String fileName) {
        try {
            // 1. 切换到文件所在目录
            if (!ftpClient.changeWorkingDirectory(filePath)) {
                System.err.println("            无法切换到目录: " + filePath);
                return null;
            }

            // 2. 获取文件的输入流
            InputStream inputStream = ftpClient.retrieveFileStream(fileName);
            if (inputStream == null) {
                System.err.println("            无法获取文件流: " + fileName);
                return null;
            }

            return inputStream;

        } catch (IOException e) {
            System.err.println("            获取文件输入流失败: " + fileName + ", 错误: " + e.getMessage());
            return null;
        }
    }

    /**
     * 完成文件传输操作，结束上一条基于数据流的传输命令
     *
     * 重要说明：
     * 这是FTP协议的重要机制，必须在使用流式传输方法后调用
     * <p>
     * 必须调用的场景（先关流，再调用）：
     * - 使用 retrieveFileStream(...) 下载完毕后
     * - 使用 storeFileStream(...)/appendFileStream(...) 上传完毕后
     * - 注意：需把流读/写到结束并close，再调用此方法
     * <p>
     * 不需要调用的场景：
     * - 使用便捷方法 storeFile(...)/retrieveFile(...)（这些内部已处理）
     * - listFiles/delete/rename/changeWorkingDirectory 等非流式命令
     * <p>
     * 不调用的后果：
     * - 下一个FTP命令报错/阻塞（常见 425/426/550）
     * - 出现"上传/下载失败""连接忙/未完成上一命令"等问题
     * <p>
     * 使用要点：
     * - 仅调用一次，且在对应流 close 之后
     * - 若抛异常，也应在 finally 中尝试调用，确保释放占用
     *
     * @param ftpClient FTP客户端实例
     */
    public static void completeFileTransfer(FTPClient ftpClient) {
        try {
            ftpClient.completePendingCommand();
        } catch (IOException e) {
            System.err.println("            完成文件传输失败: " + e.getMessage());
        }
    }

    /**
     * 判断指定目录下是否存在给定文件
     * <p>
     * 检查流程：
     * 1. 获取目录下的所有文件列表
     * 2. 遍历文件列表
     * 3. 检查是否存在指定名称的文件
     *
     * @param ftpClient FTP客户端实例
     * @param dirPath 目录路径
     * @param fileName 文件名
     * @return 文件是否存在
     * @throws IOException IO异常
     */
    private static boolean fileExists(FTPClient ftpClient, String dirPath, String fileName) throws IOException {
        // 1. 获取目录下的所有文件列表
        FTPFile[] files = ftpClient.listFiles(dirPath);
        if (files == null) {
            return false;
        }

        // 2. 遍历文件列表，查找指定文件
        for (FTPFile file : files) {
            if (file.isFile() && file.getName().equals(fileName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 将字节数组上传到FTP服务器
     *
     * 上传流程：
     * 1. 切换到目标目录
     * 2. 创建字节数组输入流
     * 3. 上传文件到FTP服务器
     *
     * @param ftpClient FTP客户端
     * @param data 要上传的数据
     * @param targetDirPath 目标目录路径
     * @param fileName 文件名
     * @throws IOException 上传过程中的IO异常
     */
    public static void uploadBytesToFTP(FTPClient ftpClient, byte[] data, String targetDirPath, String fileName) throws IOException {
        //System.out.println("开始上传字节数组到FTP: " + targetDirPath + "/" + fileName + ", 数据大小: " + data.length + " 字节");

        // 1. 切换到目标目录
        if (!ftpClient.changeWorkingDirectory(targetDirPath)) {
            throw new IOException("无法切换到目标目录: " + targetDirPath);
        }
        //System.out.println("已切换到FTP目标目录: " + targetDirPath);

        // 2. 创建字节数组输入流
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data)) {
            // 3. 上传文件到FTP服务器
            boolean success = ftpClient.storeFile(fileName, inputStream);
            if (!success) {
                throw new IOException("上传文件失败: " + fileName +
                        ", FTP回复码: " + ftpClient.getReplyCode() +
                        ", 回复信息: " + ftpClient.getReplyString());
            }
            //System.out.println("字节数组上传到FTP成功: " + fileName);
        }
    }
}
